/*****************************************************************************
 * decoder.c: Functions for the management of decoders
 *****************************************************************************
 * Copyright (C) 1999-2019 VLC authors, VideoLAN and Videolabs SAS
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
 *          Gildas Bazin <gbazin@videolan.org>
 *          Laurent Aimar <fenrir@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <stdatomic.h>

#include <vlc_common.h>
#include <vlc_block.h>
#include <vlc_vout.h>
#include <vlc_aout.h>
#include <vlc_sout.h>
#include <vlc_codec.h>
#include <vlc_spu.h>
#include <vlc_meta.h>
#include <vlc_dialog.h>
#include <vlc_modules.h>
#include <vlc_decoder.h>
#include <vlc_picture_pool.h>
#include <vlc_tracer.h>
#include <vlc_list.h>

#include "audio_output/aout_internal.h"
#include "stream_output/stream_output.h"
#include "../clock/clock.h"
#include "input_internal.h"
#include "decoder.h"
#include "resource.h"
#include "libvlc.h"

#include "../video_output/vout_internal.h"


/**
 * \file src/input/decoder.c
 *
 * The input decoder connects the input client pushing data to the
 * decoder implementation (through the matching elementary stream)
 * and the following output for audio, video and subtitles.
 *
 * It follows the locking rules below:
 *
 *  - The fifo cannot be locked when calling function from the
 *    decoder module implementation.
 *
 *  - However, the decoder module implementation might indirectly
 *    lock the fifo when calling the owner methods, in particular
 *    to send a frame or update the output status.
 *
 *  - The input code can lock the fifo to modify the global state
 *    of the input decoder.
 *
 * Backpressure preventing starvation is done by the pacing of the
 * decoder, the calls into the decoder implementation, and the
 * limits of the fifo queue.
 *
 * Basically a very fast decoder will often wait since the fifo will be
 * consumed really quickly and thus almost never stay under the lock.
 * Likewise, when the decoder is slower and the fifo can grow, it also
 * means that the decoder thread will wait more often on the
 * `decoder_t::pf_decode` call, which is done without the fifo lock as
 * per above rules.
 *
 * In addition with the standard input/output cycle from the decoder,
 * the video decoders can create sub-decoders for the closed captions
 * support embedded in the supplementary information from the codecs.
 *
 * To do so, they need to create a `decoder_cc_desc_t` matching with the
 * format that needs to be described (number of channels, type of
 * channels) and they then create them along with the closed-captions
 * content with `decoder_QueueCc`.
 *
 * In the `input/decoder.c` code, the access to the sub-decoders in the
 * subdecs.list table is protected through the `subdecs.lock` mutex.
 * Taking this lock ensures that the sub-decoder won't get
 * asynchronously removed while using it, and any mutex from the
 * sub-decoder can then be taken under this lock.
 **/

/*
 * Possibles values set in p_owner->reload atomic
 */
enum reload
{
    RELOAD_NO_REQUEST,
    RELOAD_DECODER,     /* Reload the decoder module */
    RELOAD_DECODER_AOUT /* Stop the aout and reload the decoder module */
};

struct vlc_input_decoder_t
{
    decoder_t        dec;
    es_format_t      dec_fmt_in;
    input_resource_t*p_resource;
    vlc_clock_t     *p_clock;
    const char *psz_id;

    bool hw_dec;

    const struct vlc_input_decoder_callbacks *cbs;
    void *cbs_userdata;

    ssize_t          i_spu_channel;
    int64_t          i_spu_order;

    sout_stream_t           *p_sout;
    sout_packetizer_input_t *p_sout_input;

    vlc_thread_t     thread;

    /* Some decoders require already packetized data (ie. not truncated) */
    decoder_t *p_packetizer;
    es_format_t pktz_fmt_in;
    bool b_packetizer;

    /* Current format in use by the output */
    es_format_t    fmt;
    vlc_video_context *vctx;

    /* */
    bool           b_fmt_description;
    vlc_meta_t     *p_description;
    atomic_int     reload;

    /* fifo */
    block_fifo_t *p_fifo;

    /* Lock for communication with decoder thread */
    vlc_cond_t  wait_request;
    vlc_cond_t  wait_acknowledge;
    vlc_cond_t  wait_fifo; /* TODO: merge with wait_acknowledge */

    /* pool to use when the decoder doesn't use its own */
    struct picture_pool_t *out_pool;

    /*
     * 3 threads can read/write these output variables, the DecoderThread, the
     * input thread, and the ModuleThread. The ModuleThread is either the
     * DecoderThread for synchronous modules or any thread for asynchronous
     * modules.
     *
     * Asynchronous modules are responsible for serializing/locking every
     * output calls in any thread as long as the decoder_UpdateVideoFormat() or
     * decoder_NewPicture() calls are not concurrent, cf.
     * decoder_UpdateVideoFormat() and decoder_NewPicture() notes.
     *
     * The ModuleThread is the owner of these variables, it should hold
     * the lock when writing them but doesn't have to hold it when using them.
     *
     * The DecoderThread should always hold the lock when reading/using
     * aout/vouts.
     *
     * The input thread can read these variables in order to stop outputs, when
     * both ModuleThread and DecoderThread are stopped (from DecoderDelete()).
     */

    /* If p_aout is valid, then p_astream is valid too */
    audio_output_t *p_aout;
    vlc_aout_stream *p_astream;

    vout_thread_t   *p_vout;
    bool             vout_started;
    enum vlc_vout_order vout_order;

    /* -- Theses variables need locking on read *and* write -- */
    /* Preroll */
    vlc_tick_t i_preroll_end;

#define PREROLL_NONE   VLC_TICK_MIN
#define PREROLL_FORCED VLC_TICK_MAX

    /* Pause & Rate */
    vlc_tick_t pause_date;
    vlc_tick_t delay, output_delay;
    float rate, output_rate;
    unsigned frames_countdown;
    bool paused, output_paused;

    bool error;

    /* Waiting */
    bool b_waiting;
    bool b_first;
    bool b_has_data;

    /* Flushing */
    bool flushing;
    bool b_draining;
    bool b_idle;
    bool aborting;

    /* Sub decs */
    struct
    {
        vlc_mutex_t lock;
        struct vlc_list list;
    } subdecs;

    /* CC */
    struct
    {
        /* All members guarded by subdecs.lock */
        size_t count;
        vlc_fourcc_t selected_codec;
        bool b_supported;
        decoder_cc_desc_t desc;
        bool desc_changed;
        bool b_sout_created;
        sout_packetizer_input_t *p_sout_input;
        char *sout_es_id;
    } cc;

    vlc_input_decoder_t *master_dec;

    struct vlc_list node;

    /* Mouse event */
    vlc_mutex_t     mouse_lock;
    vlc_mouse_event mouse_event;
    void           *mouse_opaque;
};

/* Pictures which are DECODER_BOGUS_VIDEO_DELAY or more in advance probably have
 * a bogus PTS and won't be displayed */
#define DECODER_BOGUS_VIDEO_DELAY                ((vlc_tick_t)(DEFAULT_PTS_DELAY * 30))

/* */
#define DECODER_SPU_VOUT_WAIT_DURATION   VLC_TICK_FROM_MS(200)
#define BLOCK_FLAG_CORE_PRIVATE_RELOADED (1 << BLOCK_FLAG_CORE_PRIVATE_SHIFT)

#define decoder_Notify(decoder_priv, event, ...) \
    if (decoder_priv->cbs && decoder_priv->cbs->event) \
        decoder_priv->cbs->event(decoder_priv, __VA_ARGS__, \
                                 decoder_priv->cbs_userdata);

static inline vlc_input_decoder_t *dec_get_owner( decoder_t *p_dec )
{
    return container_of( p_dec, vlc_input_decoder_t, dec );
}

/**
 * When the input decoder is being used only for packetizing (happen in stream output
 * configuration.), there's no need to spawn a decoder thread. The input_decoder is then considered
 * *synchronous*.
 *
 * @retval true When no decoder thread will be spawned.
 * @retval false When a decoder thread will be spawned.
 */
static inline bool vlc_input_decoder_IsSynchronous( const vlc_input_decoder_t *dec )
{
    return dec->p_sout != NULL;
}

static void Decoder_ChangeOutputPause( vlc_input_decoder_t *p_owner, bool paused, vlc_tick_t date )
{
    vlc_fifo_Assert(p_owner->p_fifo);

    decoder_t *p_dec = &p_owner->dec;

    switch( p_dec->fmt_in->i_cat )
    {
        case VIDEO_ES:
            msg_Dbg( p_dec, "%s video", paused ? "pausing" : "resuming" );
            if( p_owner->p_vout != NULL && p_owner->vout_started )
                vout_ChangePause( p_owner->p_vout, paused, date );
            break;
        case AUDIO_ES:
            msg_Dbg( p_dec, "%s audio", paused ? "pausing" : "resuming" );
            if( p_owner->p_astream != NULL )
                vlc_aout_stream_ChangePause( p_owner->p_astream, paused, date );
            break;
        case SPU_ES:
            msg_Dbg( p_dec, "not really %s SPU", paused ? "pausing" : "resuming" );
            break;
        default:
            vlc_assert_unreachable();
    }
    p_owner->output_paused = paused;
}

static void Decoder_ChangeOutputRate( vlc_input_decoder_t *p_owner, float rate )
{
    vlc_fifo_Assert(p_owner->p_fifo);

    decoder_t *p_dec = &p_owner->dec;

    msg_Dbg( p_dec, "changing rate: %f", rate );
    switch( p_dec->fmt_in->i_cat )
    {
        case VIDEO_ES:
            if( p_owner->p_vout != NULL && p_owner->vout_started )
                vout_ChangeRate( p_owner->p_vout, rate );
            break;
        case AUDIO_ES:
            if( p_owner->p_astream != NULL )
                vlc_aout_stream_ChangeRate( p_owner->p_astream, rate );
            break;
        case SPU_ES:
            if( p_owner->p_vout != NULL )
            {
                assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
                vout_ChangeSpuRate(p_owner->p_vout, p_owner->i_spu_channel,
                                   rate );
            }
            break;
        default:
            vlc_assert_unreachable();
    }
    p_owner->output_rate = rate;
}

static void Decoder_ChangeOutputDelay( vlc_input_decoder_t *p_owner, vlc_tick_t delay )
{
    vlc_fifo_Assert(p_owner->p_fifo);

    decoder_t *p_dec = &p_owner->dec;

    msg_Dbg( p_dec, "changing delay: %"PRId64, delay );
    switch( p_dec->fmt_in->i_cat )
    {
        case VIDEO_ES:
            if( p_owner->p_vout != NULL && p_owner->vout_started )
                vout_ChangeDelay( p_owner->p_vout, delay );
            break;
        case AUDIO_ES:
            if( p_owner->p_astream != NULL )
                vlc_aout_stream_ChangeDelay( p_owner->p_astream, delay );
            break;
        case SPU_ES:
            if( p_owner->p_vout != NULL )
            {
                assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
                vout_ChangeSpuDelay(p_owner->p_vout, p_owner->i_spu_channel,
                                    delay);
            }
            break;
        default:
            vlc_assert_unreachable();
    }
    p_owner->output_delay = delay;
}

static void Decoder_UpdateOutState(vlc_input_decoder_t *owner)
{
    if (owner->paused)
        Decoder_ChangeOutputPause(owner, owner->paused, owner->pause_date);

    if (owner->rate != 1.f)
        Decoder_ChangeOutputRate(owner, owner->rate);

    if (owner->delay != 0)
        Decoder_ChangeOutputDelay(owner, owner->delay);
}

/**
 * Load a decoder module
 */
static int LoadDecoder(decoder_t *p_dec, bool b_packetizer, es_format_t *fmt_in)
{
    p_dec->b_frame_drop_allowed = true;

    /* Find a suitable decoder/packetizer module */
    if( !b_packetizer )
    {
        static const char caps[ES_CATEGORY_COUNT][16] = {
            [VIDEO_ES] = "video decoder",
            [AUDIO_ES] = "audio decoder",
            [SPU_ES] = "spu decoder",
        };
        p_dec->p_module = module_need_var( p_dec, caps[p_dec->fmt_in->i_cat],
                                           "codec" );
    }
    else
        p_dec->p_module = module_need_var( p_dec, "packetizer", "packetizer" );

    if( !p_dec->p_module )
    {
        es_format_Clean(fmt_in);
        decoder_Clean( p_dec );
        return -1;
    }
    return 0;
}

static int DecoderThread_Reload( vlc_input_decoder_t *p_owner,
                                 const es_format_t *restrict p_fmt,
                                 enum reload reload )
{
    /* Copy p_fmt since it can be destroyed by decoder_Clean */
    decoder_t *p_dec = &p_owner->dec;
    es_format_t fmt_in;
    if( es_format_Copy( &fmt_in, p_fmt ) != VLC_SUCCESS )
    {
        p_owner->error = true;
        return VLC_EGENERIC;
    }

    /* Restart the decoder module */
    vlc_fifo_Unlock(p_owner->p_fifo);
    decoder_Clean( p_dec );
    vlc_fifo_Lock(p_owner->p_fifo);
    es_format_Clean( &p_owner->dec_fmt_in );
    p_owner->error = false;

    if( reload == RELOAD_DECODER_AOUT )
    {
        assert( p_owner->fmt.i_cat == AUDIO_ES );
        audio_output_t *p_aout = p_owner->p_aout;
        vlc_aout_stream *p_astream = p_owner->p_astream;
        // no need to lock, the decoder and ModuleThread are dead
        p_owner->p_aout = NULL;
        p_owner->p_astream = NULL;
        if( p_aout )
        {
            assert( p_astream );
            vlc_aout_stream_Delete( p_astream );
            input_resource_PutAout( p_owner->p_resource, p_aout );
        }
    }

    decoder_Init(p_dec, &p_owner->dec_fmt_in, &fmt_in);
    vlc_fifo_Unlock(p_owner->p_fifo);
    if (LoadDecoder(p_dec, false, &p_owner->dec_fmt_in))
    {
        vlc_fifo_Lock(p_owner->p_fifo);
        p_owner->error = true;
        es_format_Clean( &fmt_in );
        return VLC_EGENERIC;
    }
    vlc_fifo_Lock(p_owner->p_fifo);
    es_format_Clean( &fmt_in );
    return VLC_SUCCESS;
}

static void DecoderUpdateFormatLocked( vlc_input_decoder_t *p_owner )
{
    decoder_t *p_dec = &p_owner->dec;

    vlc_fifo_Assert(p_owner->p_fifo);

    es_format_Clean( &p_owner->fmt );
    es_format_Copy( &p_owner->fmt, &p_dec->fmt_out );

    assert( p_owner->fmt.i_cat == p_dec->fmt_in->i_cat );

    /* Move p_description */
    if( p_dec->p_description != NULL )
    {
        if( p_owner->p_description != NULL )
            vlc_meta_Delete( p_owner->p_description );
        p_owner->p_description = p_dec->p_description;
        p_dec->p_description = NULL;
    }

    p_owner->b_fmt_description = true;
}

static void MouseEvent( const vlc_mouse_t *newmouse, void *user_data )
{
    decoder_t *dec = user_data;
    vlc_input_decoder_t *owner = dec_get_owner( dec );

    vlc_mutex_lock( &owner->mouse_lock );
    if( owner->mouse_event )
        owner->mouse_event( newmouse, owner->mouse_opaque);
    vlc_mutex_unlock( &owner->mouse_lock );
}

/*****************************************************************************
 * Buffers allocation callbacks for the decoders
 *****************************************************************************/
static bool aout_replaygain_changed( const audio_replay_gain_t *a,
                                     const audio_replay_gain_t *b )
{
    for( size_t i=0; i<AUDIO_REPLAY_GAIN_MAX; i++ )
    {
        if( a->pb_gain[i] != b->pb_gain[i] ||
            a->pb_peak[i] != b->pb_peak[i] ||
            (a->pb_gain[i] && a->pf_gain[i] != b->pf_gain[i]) ||
            (a->pb_peak[i] && a->pf_peak[i] != b->pf_peak[i]) )
            return true;
    }
    return false;
}

static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );

    if( p_owner->p_aout &&
       ( !AOUT_FMTS_IDENTICAL(&p_dec->fmt_out.audio, &p_owner->fmt.audio) ||
         p_dec->fmt_out.i_codec != p_dec->fmt_out.audio.i_format ||
         p_dec->fmt_out.i_profile != p_owner->fmt.i_profile ) )
    {
        audio_output_t *p_aout = p_owner->p_aout;
        vlc_aout_stream *p_astream = p_owner->p_astream;

        /* Parameters changed, restart the aout */
        vlc_fifo_Lock(p_owner->p_fifo);
        p_owner->p_astream = NULL;
        p_owner->p_aout = NULL; // the DecoderThread should not use the old aout anymore
        vlc_fifo_Unlock(p_owner->p_fifo);
        vlc_aout_stream_Delete( p_astream );

        input_resource_PutAout( p_owner->p_resource, p_aout );
    }

    /* Check if only replay gain has changed */
    if( aout_replaygain_changed( &p_dec->fmt_in->audio_replay_gain,
                                 &p_owner->fmt.audio_replay_gain ) )
    {
        p_dec->fmt_out.audio_replay_gain = p_dec->fmt_in->audio_replay_gain;
        if( p_owner->p_aout )
        {
            p_owner->fmt.audio_replay_gain = p_dec->fmt_in->audio_replay_gain;
            var_TriggerCallback( p_owner->p_aout, "audio-replay-gain-mode" );
        }
    }

    if( p_owner->p_aout == NULL )
    {
        p_dec->fmt_out.audio.i_format = p_dec->fmt_out.i_codec;

        audio_sample_format_t format = p_dec->fmt_out.audio;
        aout_FormatPrepare( &format );

        const int i_force_dolby = var_InheritInteger( p_dec, "force-dolby-surround" );
        if( i_force_dolby &&
            format.i_physical_channels == (AOUT_CHAN_LEFT|AOUT_CHAN_RIGHT) )
        {
            if( i_force_dolby == 1 )
                format.i_chan_mode |= AOUT_CHANMODE_DOLBYSTEREO;
            else /* i_force_dolby == 2 */
                format.i_chan_mode &= ~AOUT_CHANMODE_DOLBYSTEREO;
        }

        audio_output_t *p_aout;
        vlc_aout_stream *p_astream;

        p_aout = input_resource_GetAout( p_owner->p_resource );
        if( p_aout )
        {
            const struct vlc_aout_stream_cfg cfg = {
                .fmt = &format,
                .profile = p_dec->fmt_out.i_profile,
                .clock = p_owner->p_clock,
                .str_id = p_owner->psz_id,
                .replay_gain = &p_dec->fmt_out.audio_replay_gain,
            };
            p_astream = vlc_aout_stream_New( p_aout, &cfg );
            if( p_astream == NULL )
            {
                input_resource_PutAout( p_owner->p_resource, p_aout );
                p_aout = NULL;
            }
        }
        else
            p_astream = NULL;

        vlc_fifo_Lock(p_owner->p_fifo);
        p_owner->p_aout = p_aout;
        p_owner->p_astream = p_astream;

        DecoderUpdateFormatLocked( p_owner );
        aout_FormatPrepare( &p_owner->fmt.audio );

        if( p_aout == NULL )
        {
            vlc_fifo_Unlock(p_owner->p_fifo);
            return -1;
        }

        p_dec->fmt_out.audio.i_bytes_per_frame =
            p_owner->fmt.audio.i_bytes_per_frame;
        p_dec->fmt_out.audio.i_bitspersample =
            p_owner->fmt.audio.i_bitspersample;
        p_dec->fmt_out.audio.i_frame_length =
            p_owner->fmt.audio.i_frame_length;

        Decoder_UpdateOutState( p_owner );
        vlc_fifo_Unlock( p_owner->p_fifo );
    }
    return 0;
}

static int CreateVoutIfNeeded(vlc_input_decoder_t *);


static int ModuleThread_UpdateVideoFormat( decoder_t *p_dec, vlc_video_context *vctx )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );

    int created_vout = CreateVoutIfNeeded(p_owner);
    if (created_vout == -1)
        return -1; // error
    if (created_vout == 0)
    {
        // video context didn't change
        if (vctx != NULL && p_owner->vctx == vctx)
            return 0;
    }
    assert(p_owner->p_vout);

    if (p_owner->vctx)
        vlc_video_context_Release(p_owner->vctx);
    p_owner->vctx = vctx ? vlc_video_context_Hold(vctx) : NULL;

    // configure the new vout
    vlc_fifo_Lock(p_owner->p_fifo);
    if ( p_owner->out_pool == NULL )
    {
        unsigned dpb_size;
        switch( p_dec->fmt_in->i_codec )
        {
        case VLC_CODEC_HEVC:
        case VLC_CODEC_H264:
        case VLC_CODEC_DIRAC: /* FIXME valid ? */
        case VLC_CODEC_VVC:
            dpb_size = 18;
            break;
        case VLC_CODEC_AV1:
            dpb_size = 8; /* NUM_REF_FRAMES from the AV1 spec */
            break;
        case VLC_CODEC_MP4V:
        case VLC_CODEC_VP5:
        case VLC_CODEC_VP6:
        case VLC_CODEC_VP6F:
        case VLC_CODEC_VP8:
            dpb_size = 3;
            break;
        default:
            dpb_size = 2;
            break;
        }
        picture_pool_t *pool = picture_pool_NewFromFormat( &p_dec->fmt_out.video,
                            dpb_size + p_dec->i_extra_picture_buffers + 1 );

        if( pool == NULL)
        {
            msg_Err(p_dec, "Failed to create a pool of %d %4.4s pictures",
                           dpb_size + p_dec->i_extra_picture_buffers + 1,
                           (char*)&p_dec->fmt_out.video.i_chroma);
            vlc_fifo_Unlock(p_owner->p_fifo);
            goto error;
        }

        p_owner->out_pool = pool;
    }

    vout_configuration_t cfg = {
        .vout = p_owner->p_vout, .clock = p_owner->p_clock,
        .str_id = p_owner->psz_id,
        .fmt = &p_dec->fmt_out.video,
        .mouse_event = MouseEvent, .mouse_opaque = p_dec,
    };
    vlc_fifo_Unlock(p_owner->p_fifo);

    enum input_resource_vout_state vout_state;
    vout_thread_t *p_vout =
        input_resource_RequestVout(p_owner->p_resource, vctx, &cfg, NULL,
                                   &vout_state);
    if (p_vout != NULL)
    {
        assert(vout_state == INPUT_RESOURCE_VOUT_NOTCHANGED ||
               vout_state == INPUT_RESOURCE_VOUT_STARTED);

        vlc_fifo_Lock(p_owner->p_fifo);
        p_owner->vout_started = true;

        if (vout_state == INPUT_RESOURCE_VOUT_STARTED)
        {
            Decoder_UpdateOutState( p_owner );
            vlc_fifo_Unlock(p_owner->p_fifo);

            decoder_Notify(p_owner, on_vout_started, p_vout, p_owner->vout_order);
        }
        else
            vlc_fifo_Unlock(p_owner->p_fifo);

        return 0;
    }
    else
    {
        assert(vout_state == INPUT_RESOURCE_VOUT_NOTCHANGED ||
               vout_state == INPUT_RESOURCE_VOUT_STOPPED);

        if (vout_state == INPUT_RESOURCE_VOUT_STOPPED)
            decoder_Notify(p_owner, on_vout_stopped, cfg.vout);
    }

error:
    /* Clean fmt and vctx to trigger a new vout creation on the next update
     * call */
    vlc_fifo_Lock(p_owner->p_fifo);
    es_format_Clean( &p_owner->fmt );
    vlc_fifo_Unlock(p_owner->p_fifo);

    if (p_owner->vctx != NULL)
    {
        vlc_video_context_Release(p_owner->vctx);
        p_owner->vctx = NULL;
    }
    return -1;
}

static int CreateVoutIfNeeded(vlc_input_decoder_t *p_owner)
{
    decoder_t *p_dec = &p_owner->dec;
    bool need_vout = false;

    vlc_fifo_Lock(p_owner->p_fifo);
    if( p_owner->p_vout == NULL )
    {
        msg_Dbg(p_dec, "vout: none found");
        need_vout = true;
    }
    if( p_dec->fmt_out.video.i_width != p_owner->fmt.video.i_width
             || p_dec->fmt_out.video.i_height != p_owner->fmt.video.i_height )
    {
        msg_Dbg(p_dec, "vout change: decoder size");
        need_vout = true;
    }
    if( p_dec->fmt_out.video.i_visible_width != p_owner->fmt.video.i_visible_width
             || p_dec->fmt_out.video.i_visible_height != p_owner->fmt.video.i_visible_height
             || p_dec->fmt_out.video.i_x_offset != p_owner->fmt.video.i_x_offset
             || p_dec->fmt_out.video.i_y_offset != p_owner->fmt.video.i_y_offset )
    {
        msg_Dbg(p_dec, "vout change: visible size");
        need_vout = true;
    }
    if( p_dec->fmt_out.i_codec != p_owner->fmt.video.i_chroma )
    {
        msg_Dbg(p_dec, "vout change: chroma");
        need_vout = true;
    }
    if( (int64_t)p_dec->fmt_out.video.i_sar_num * p_owner->fmt.video.i_sar_den !=
             (int64_t)p_dec->fmt_out.video.i_sar_den * p_owner->fmt.video.i_sar_num )
    {
        msg_Dbg(p_dec, "vout change: SAR");
        need_vout = true;
    }
    if( p_dec->fmt_out.video.orientation != p_owner->fmt.video.orientation )
    {
        msg_Dbg(p_dec, "vout change: orientation");
        need_vout = true;
    }
    if( p_dec->fmt_out.video.multiview_mode != p_owner->fmt.video.multiview_mode )
    {
        msg_Dbg(p_dec, "vout change: multiview");
        need_vout = true;
    }

    if( !need_vout )
    {
        vlc_fifo_Unlock(p_owner->p_fifo);
        return 0; // vout unchanged
    }

    vout_thread_t *p_vout = p_owner->p_vout;
    p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
    p_owner->vout_started = false;
    vlc_fifo_Unlock( p_owner->p_fifo );

    enum vlc_vout_order order;
    const vout_configuration_t cfg = { .vout = p_vout, .fmt = NULL };
    p_vout = input_resource_RequestVout( p_owner->p_resource, NULL, &cfg, &order, NULL );

    vlc_fifo_Lock( p_owner->p_fifo );
    p_owner->p_vout = p_vout;
    p_owner->vout_order = order;

    DecoderUpdateFormatLocked( p_owner );
    p_owner->fmt.video.i_chroma = p_dec->fmt_out.i_codec;
    picture_pool_t *pool = p_owner->out_pool;
    p_owner->out_pool = NULL;
    vlc_fifo_Unlock( p_owner->p_fifo );

     if ( pool != NULL )
         picture_pool_Release( pool );

    if( p_vout == NULL )
    {
        msg_Err( p_dec, "failed to create video output" );
        return -1;
    }

    return 1; // new vout was created
}

static vlc_decoder_device * ModuleThread_GetDecoderDevice( decoder_t *p_dec )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );

    /* Requesting a decoder device will automatically enable hw decoding */
    if (!p_owner->hw_dec)
        return NULL;

    int created_vout = CreateVoutIfNeeded(p_owner);
    if (created_vout == -1)
        return NULL;  // error

    assert(p_owner->p_vout);
    vlc_decoder_device *dec_device = vout_GetDevice(p_owner->p_vout);
    if (created_vout == 1)
        return dec_device; // new vout was created with a decoder device

    bool need_format_update = false;
    if ( memcmp( &p_dec->fmt_out.video.mastering,
                 &p_owner->fmt.video.mastering,
                 sizeof(p_owner->fmt.video.mastering)) )
    {
        msg_Dbg(p_dec, "vout update: mastering data");
        need_format_update = true;
    }
    if ( p_dec->fmt_out.video.lighting.MaxCLL !=
         p_owner->fmt.video.lighting.MaxCLL ||
         p_dec->fmt_out.video.lighting.MaxFALL !=
         p_owner->fmt.video.lighting.MaxFALL )
    {
        msg_Dbg(p_dec, "vout update: lighting data");
        need_format_update = true;
    }

    if ( need_format_update )
    {
        /* the format has changed but we don't need a new vout */
        vlc_fifo_Lock(p_owner->p_fifo);
        DecoderUpdateFormatLocked( p_owner );
        vlc_fifo_Unlock(p_owner->p_fifo);
    }
    return dec_device;
}

static picture_t *ModuleThread_NewVideoBuffer( decoder_t *p_dec )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    assert( p_owner->p_vout );
    assert( p_owner->out_pool );

    picture_t *pic = picture_pool_Wait( p_owner->out_pool );
    if (pic)
        picture_Reset( pic );
    return pic;
}

static subpicture_t *ModuleThread_NewSpuBuffer( decoder_t *p_dec,
                                     const subpicture_updater_t *p_updater )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    vout_thread_t *p_vout = NULL;
    subpicture_t *p_subpic;
    int i_attempts = 30;

    while( i_attempts-- )
    {
        if( p_owner->error )
            break;

        p_vout = input_resource_HoldVout( p_owner->p_resource );
        if( p_vout )
            break;

        vlc_tick_sleep( DECODER_SPU_VOUT_WAIT_DURATION );
    }

    if( !p_vout )
    {
        msg_Warn( p_dec, "no vout found, dropping subpicture" );
        if( p_owner->p_vout )
        {
            assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
            decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);

            vlc_fifo_Lock( p_owner->p_fifo );
            vout_UnregisterSubpictureChannel(p_owner->p_vout,
                                             p_owner->i_spu_channel);
            p_owner->i_spu_channel = VOUT_SPU_CHANNEL_INVALID;

            vout_Release(p_owner->p_vout);
            p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
            vlc_fifo_Unlock( p_owner->p_fifo );
        }
        return NULL;
    }

    if( p_owner->p_vout != p_vout )
    {
        if (p_owner->p_vout) /* notify the previous vout deletion unlocked */
            decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);

        vlc_fifo_Lock(p_owner->p_fifo);

        if (p_owner->p_vout)
        {
            /* Unregister the SPU channel of the previous vout */
            assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
            vout_UnregisterSubpictureChannel(p_owner->p_vout,
                                             p_owner->i_spu_channel);
            vout_Release(p_owner->p_vout);
            p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
        }

        enum vlc_vout_order channel_order;
        p_owner->i_spu_channel =
            vout_RegisterSubpictureChannelInternal(p_vout, p_owner->p_clock,
                                                   &channel_order);
        p_owner->i_spu_order = 0;

        if (p_owner->i_spu_channel == VOUT_SPU_CHANNEL_INVALID)
        {
            /* The new vout doesn't support SPU, aborting... */
            vlc_fifo_Unlock(p_owner->p_fifo);
            vout_Release(p_vout);
            return NULL;
        }

        p_owner->p_vout = p_vout;
        p_owner->vout_order = channel_order;
        Decoder_UpdateOutState(p_owner);
        vlc_fifo_Unlock(p_owner->p_fifo);

        assert(channel_order != VLC_VOUT_ORDER_NONE);
        decoder_Notify(p_owner, on_vout_started, p_vout, channel_order);
    }
    else
        vout_Release(p_vout);

    p_subpic = subpicture_New( p_updater );
    if( p_subpic )
    {
        p_subpic->i_channel = p_owner->i_spu_channel;
        p_subpic->i_order = p_owner->i_spu_order++;
    }

    return p_subpic;
}

static int InputThread_GetInputAttachments( decoder_t *p_dec,
                                       input_attachment_t ***ppp_attachment,
                                       int *pi_attachment )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    if (!p_owner->cbs || !p_owner->cbs->get_attachments)
        return VLC_EINVAL;

    int ret = p_owner->cbs->get_attachments(p_owner, ppp_attachment,
                                            p_owner->cbs_userdata);
    if (ret < 0)
        return VLC_EGENERIC;
    *pi_attachment = ret;
    return VLC_SUCCESS;
}

static vlc_tick_t ModuleThread_GetDisplayDate( decoder_t *p_dec,
                                       vlc_tick_t system_now, vlc_tick_t i_ts )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );

    vlc_fifo_Lock(p_owner->p_fifo);
    if( p_owner->b_waiting || p_owner->paused )
        i_ts = VLC_TICK_INVALID;
    float rate = p_owner->output_rate;
    vlc_fifo_Unlock(p_owner->p_fifo);

    if( !p_owner->p_clock || i_ts == VLC_TICK_INVALID )
        return i_ts;

    vlc_clock_Lock( p_owner->p_clock );
    vlc_tick_t conv_ts =
        vlc_clock_ConvertToSystem( p_owner->p_clock, system_now, i_ts, rate, NULL );
    vlc_clock_Unlock( p_owner->p_clock );
    return conv_ts;
}

static float ModuleThread_GetDisplayRate( decoder_t *p_dec )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );

    if( !p_owner->p_clock )
        return 1.f;
    vlc_fifo_Lock(p_owner->p_fifo);
    float rate = p_owner->output_rate;
    vlc_fifo_Unlock(p_owner->p_fifo);
    return rate;
}

/*****************************************************************************
 * Public functions
 *****************************************************************************/
vlc_frame_t *decoder_NewAudioBuffer( decoder_t *dec, int samples )
{
    assert( dec->fmt_out.audio.i_frame_length > 0
         && dec->fmt_out.audio.i_bytes_per_frame  > 0 );

    size_t length = samples * dec->fmt_out.audio.i_bytes_per_frame
                            / dec->fmt_out.audio.i_frame_length;
    vlc_frame_t *frame = block_Alloc( length );
    if( likely(frame != NULL) )
    {
        frame->i_nb_samples = samples;
        frame->i_pts = frame->i_length = 0;
    }
    return frame;
}

static void RequestReload( vlc_input_decoder_t *p_owner )
{
    /* Don't override reload if it's RELOAD_DECODER_AOUT */
    int expected = RELOAD_NO_REQUEST;
    atomic_compare_exchange_strong( &p_owner->reload, &expected, RELOAD_DECODER );
}

static int DecoderWaitUnblock( vlc_input_decoder_t *p_owner )
{
    vlc_fifo_Assert(p_owner->p_fifo);

    if( p_owner->b_waiting )
    {
        p_owner->b_has_data = true;
        vlc_cond_signal( &p_owner->wait_acknowledge );
    }

    while (p_owner->b_waiting && p_owner->b_has_data && !p_owner->flushing)
        vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_request);

    if (p_owner->flushing)
    {
        p_owner->b_has_data = false;
        vlc_cond_signal(&p_owner->wait_acknowledge);
        return VLC_ENOENT;
    }

    return VLC_SUCCESS;
}

static inline void DecoderUpdatePreroll( vlc_tick_t *pi_preroll, const vlc_frame_t *p )
{
    if( p->i_flags & BLOCK_FLAG_PREROLL )
        *pi_preroll = PREROLL_FORCED;
    /* Check if we can use the packet for end of preroll */
    else if( (p->i_flags & BLOCK_FLAG_DISCONTINUITY) &&
             (p->i_buffer == 0 || (p->i_flags & BLOCK_FLAG_CORRUPTED)) )
        *pi_preroll = PREROLL_FORCED;
    else if( p->i_dts != VLC_TICK_INVALID )
        *pi_preroll = __MIN( *pi_preroll, p->i_dts );
    else if( p->i_pts != VLC_TICK_INVALID )
        *pi_preroll = __MIN( *pi_preroll, p->i_pts );
}

static void DecoderSendSubstream(vlc_input_decoder_t *p_owner)
{
    decoder_t *p_dec = &p_owner->dec;
    if (p_dec->pf_get_cc == NULL)
        return;

    bool b_wants_substreams;
    int ret = sout_StreamControl(p_owner->p_sout,
                                 SOUT_STREAM_WANTS_SUBSTREAMS,
                                 &b_wants_substreams);

    if (ret != VLC_SUCCESS || !b_wants_substreams)
        return;

    if (p_owner->cc.p_sout_input == NULL && p_owner->cc.b_sout_created)
        return;

    decoder_cc_desc_t desc;
    vlc_frame_t *p_cc = p_dec->pf_get_cc( p_dec, &desc );
    if (p_cc == NULL)
        return;

    if (!p_owner->cc.b_sout_created)
    {
        es_format_t ccfmt;
        es_format_Init(&ccfmt, SPU_ES, VLC_CODEC_CEA608);
        ccfmt.i_group = p_owner->fmt.i_group;
        ccfmt.subs.cc.i_reorder_depth = desc.i_reorder_depth;

        /* Create only one es ID since we simply send the CC whole side data
         * un-decoded to the stream output.
         * Specifying the owner's ID is important for the uniqueness for the ID.
         */
        if (asprintf(&p_owner->cc.sout_es_id, "%s/cc", p_owner->psz_id) == -1)
        {
            es_format_Clean(&ccfmt);
            block_Release(p_cc);
            return;
        }

        p_owner->cc.p_sout_input =
            sout_InputNew(p_owner->p_sout, &ccfmt, p_owner->cc.sout_es_id);

        es_format_Clean(&ccfmt);
        p_owner->cc.b_sout_created = true;
    }

    if (!p_owner->cc.p_sout_input ||
        sout_InputSendBuffer(p_owner->p_sout, p_owner->cc.p_sout_input, p_cc))
    {
        block_Release(p_cc);
    }
}

/* This function process a frame for sout
 */
static void DecoderThread_ProcessSout( vlc_input_decoder_t *p_owner, vlc_frame_t *frame )
{
    decoder_t *p_dec = &p_owner->dec;
    vlc_frame_t *sout_frame;
    vlc_frame_t **ppframe = frame ? &frame : NULL;

    while( ( sout_frame =
                 p_dec->pf_packetize( p_dec, ppframe ) ) )
    {
        if( p_owner->p_sout_input == NULL )
        {
            vlc_fifo_Lock(p_owner->p_fifo);
            DecoderUpdateFormatLocked( p_owner );

            p_owner->fmt.i_group = p_dec->fmt_in->i_group;
            p_owner->fmt.i_id = p_dec->fmt_in->i_id;
            if( p_dec->fmt_in->psz_language )
            {
                free( p_owner->fmt.psz_language );
                p_owner->fmt.psz_language =
                    strdup( p_dec->fmt_in->psz_language );
            }
            vlc_fifo_Unlock(p_owner->p_fifo);

            p_owner->p_sout_input =
                sout_InputNew( p_owner->p_sout, &p_owner->fmt, p_owner->psz_id );

            if( p_owner->p_sout_input == NULL )
            {
                msg_Err( p_dec, "cannot create packetized sout output (%4.4s)",
                         (char *)&p_owner->fmt.i_codec );
                p_owner->error = true;

                if(frame)
                    block_Release(frame);

                block_ChainRelease(sout_frame);
                break;
            }
        }

        while( sout_frame )
        {
            vlc_frame_t *p_next = sout_frame->p_next;

            sout_frame->p_next = NULL;

            DecoderSendSubstream( p_owner );

            /* FIXME --VLC_TICK_INVALID inspect stream_output*/
            if ( sout_InputSendBuffer( p_owner->p_sout, p_owner->p_sout_input, sout_frame ) ==
                 VLC_EGENERIC )
            {
                msg_Err( p_dec, "cannot continue streaming due to errors with codec %4.4s",
                                (char *)&p_owner->fmt.i_codec );

                p_owner->error = true;

                /* Cleanup */

                if( frame )
                    block_Release( frame );

                block_ChainRelease( p_next );
                return;
            }

            sout_frame = p_next;
        }
    }
}

static void GetCcChannels(vlc_input_decoder_t *owner, size_t *max_channels,
                          uint64_t *bitmap)
{
    switch (owner->cc.selected_codec)
    {
        case VLC_CODEC_CEA608:
            if (max_channels != NULL)
                *max_channels = 4;
            if (bitmap != NULL)
                *bitmap = owner->cc.desc.i_608_channels;
            break;
        case VLC_CODEC_CEA708:
            if (max_channels != NULL)
                *max_channels = 64;
            if (bitmap != NULL)
                *bitmap = owner->cc.desc.i_708_channels;
            break;
        default: vlc_assert_unreachable();
    }
}

static bool SubDecoderIsCc(vlc_input_decoder_t *subdec)
{
    return subdec->dec.fmt_in->i_cat == SPU_ES &&
            (subdec->dec.fmt_in->i_codec == VLC_CODEC_CEA608 ||
             subdec->dec.fmt_in->i_codec == VLC_CODEC_CEA708);
}

/* */
static void DecoderPlayCcLocked( vlc_input_decoder_t *p_owner, vlc_frame_t *p_cc,
                                 const decoder_cc_desc_t *p_desc )
{
    vlc_fifo_Assert(p_owner->p_fifo);
    if (p_owner->flushing || p_owner->aborting)
    {
        vlc_frame_Release(p_cc);
        return;
    }

    vlc_mutex_lock(&p_owner->subdecs.lock);
    if (p_owner->cc.desc.i_608_channels != p_desc->i_608_channels ||
        p_owner->cc.desc.i_708_channels != p_desc->i_708_channels ||
        p_owner->cc.desc.i_reorder_depth != p_desc->i_reorder_depth)
    {
        p_owner->cc.desc = *p_desc;
        p_owner->cc.desc_changed = true;
    }

    if (p_owner->cc.count == 0)
        goto end;

    size_t cc_idx = 0;
    vlc_input_decoder_t *it;

    vlc_list_foreach(it, &p_owner->subdecs.list, node)
    {
        if (!SubDecoderIsCc(it))
            continue;

        if (++cc_idx == p_owner->cc.count)
        {
            block_FifoPut(it->p_fifo, p_cc);
            p_cc = NULL;
        }
        else
        {
            block_t *dup = block_Duplicate(p_cc);
            if (dup == NULL)
                break;
            block_FifoPut(it->p_fifo, dup);
        }
    }

end:
    vlc_mutex_unlock(&p_owner->subdecs.lock);

    if (p_cc != NULL) /* can have bitmap set but no created decs */
        block_Release( p_cc );
}

static void PacketizerGetCc( vlc_input_decoder_t *p_owner, decoder_t *p_dec_cc )
{
    vlc_frame_t *p_cc;
    decoder_cc_desc_t desc;

    /* Do not try retrieving CC if not wanted (sout) or cannot be retrieved */
    if( !p_owner->cc.b_supported )
        return;

    assert( p_dec_cc->pf_get_cc != NULL );

    p_cc = p_dec_cc->pf_get_cc( p_dec_cc, &desc );
    if( !p_cc )
        return;

    DecoderPlayCcLocked( p_owner, p_cc, &desc );
}

static void ModuleThread_QueueCc( decoder_t *p_videodec, vlc_frame_t *p_cc,
                                  const decoder_cc_desc_t *p_desc )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_videodec );

    if (unlikely(p_cc == NULL))
        return;

    if (!p_owner->cc.b_supported ||
       (p_owner->p_packetizer != NULL && p_owner->p_packetizer->pf_get_cc != NULL))
    {
        block_Release(p_cc);
        return;
    }

    vlc_fifo_Lock(p_owner->p_fifo);
    DecoderPlayCcLocked(p_owner, p_cc, p_desc);
    vlc_fifo_Unlock(p_owner->p_fifo);
}

static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_picture )
{
    decoder_t *p_dec = &p_owner->dec;

    if( p_picture->date == VLC_TICK_INVALID )
        /* FIXME: VLC_TICK_INVALID -- verify video_output */
    {
        msg_Warn( p_dec, "non-dated video buffer received" );
        picture_Release( p_picture );
        return VLC_EGENERIC;
    }

    if (p_owner->flushing || p_owner->aborting)
    {
        picture_Release(p_picture);
        return VLC_SUCCESS;
    }

    vout_thread_t  *p_vout = p_owner->p_vout;

    assert( p_owner->vout_started );

    bool prerolled = p_owner->i_preroll_end != PREROLL_NONE;
    if( prerolled && p_owner->i_preroll_end > p_picture->date )
    {
        picture_Release( p_picture );
        return VLC_SUCCESS;
    }

    p_owner->i_preroll_end = PREROLL_NONE;

    if( unlikely(prerolled) )
    {
        msg_Dbg( p_dec, "end of video preroll" );

        if( p_vout )
            vout_FlushAll( p_vout );
    }

    if( p_owner->b_first && p_owner->b_waiting )
    {
        msg_Dbg( p_dec, "Received first picture" );
        p_owner->b_first = false;
        p_picture->b_force = true;
    }
    else
    {
        int ret = DecoderWaitUnblock(p_owner);
        if (ret != VLC_SUCCESS)
        {
            picture_Release(p_picture);
            return ret;
        }
    }

    if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) )
        p_owner->frames_countdown--;

    /* */
    if( p_vout == NULL )
    {
        picture_Release( p_picture );
        return VLC_EGENERIC;
    }

    if( p_picture->b_still )
    {
        /* Ensure no earlier higher pts breaks still state */
        vout_Flush( p_vout, p_picture->date );
    }
    vout_PutPicture( p_vout, p_picture );

    return VLC_SUCCESS;
}

static void ModuleThread_QueueVideo( decoder_t *p_dec, picture_t *p_pic )
{
    assert( p_pic );
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );

    if ( tracer != NULL )
    {
        vlc_tracer_TraceStreamPTS( tracer, "DEC", p_owner->psz_id,
                            "OUT", p_pic->date );
    }

    vlc_fifo_Lock( p_owner->p_fifo );

    int success = ModuleThread_PlayVideo( p_owner, p_pic );

    unsigned displayed = 0;
    unsigned vout_lost = 0;
    unsigned vout_late = 0;
    if( p_owner->p_vout != NULL )
    {
        vout_GetResetStatistic( p_owner->p_vout, &displayed, &vout_lost, &vout_late );
    }
    if (success != VLC_SUCCESS)
        vout_lost++;

    vlc_fifo_Unlock(p_owner->p_fifo);

    decoder_Notify(p_owner, on_new_video_stats, 1, vout_lost, displayed, vout_late);
}

static vlc_decoder_device * thumbnailer_get_device( decoder_t *p_dec )
{
    VLC_UNUSED(p_dec);
    // no hardware decoder on purpose
    // we don't want to load many DLLs and allocate many pictures
    // just to decode one picture
    return NULL;
}

static picture_t *thumbnailer_buffer_new( decoder_t *p_dec )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    /* Avoid decoding more than one frame when a thumbnail was
     * already generated */
    vlc_fifo_Lock(p_owner->p_fifo);
    if( !p_owner->b_first )
    {
        vlc_fifo_Unlock(p_owner->p_fifo);
        return NULL;
    }
    vlc_fifo_Unlock(p_owner->p_fifo);
    return picture_NewFromFormat( &p_dec->fmt_out.video );
}

static void ModuleThread_QueueThumbnail( decoder_t *p_dec, picture_t *p_pic )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    bool b_first;

    vlc_fifo_Lock(p_owner->p_fifo);
    b_first = p_owner->b_first;
    p_owner->b_first = false;
    vlc_fifo_Unlock(p_owner->p_fifo);

    if( b_first )
        decoder_Notify(p_owner, on_thumbnail_ready, p_pic);
    picture_Release( p_pic );

}

static int ModuleThread_PlayAudio( vlc_input_decoder_t *p_owner, vlc_frame_t *p_audio )
{
    decoder_t *p_dec = &p_owner->dec;

    assert( p_audio != NULL );

    if( p_audio->i_pts == VLC_TICK_INVALID ) // FIXME --VLC_TICK_INVALID verify audio_output/*
    {
        msg_Warn( p_dec, "non-dated audio buffer received" );
        block_Release( p_audio );
        return VLC_EGENERIC;
    }

    vlc_aout_stream *p_astream = p_owner->p_astream;
    if( p_astream == NULL )
    {
        msg_Dbg( p_dec, "discarded audio buffer" );
        block_Release( p_audio );
        return VLC_EGENERIC;
    }

    if (p_owner->flushing || p_owner->aborting)
    {
        block_Release(p_audio);
        return VLC_SUCCESS;
    }

    bool prerolled = p_owner->i_preroll_end != PREROLL_NONE;
    if( prerolled && p_owner->i_preroll_end > p_audio->i_pts )
    {
        block_Release( p_audio );
        return VLC_SUCCESS;
    }

    p_owner->i_preroll_end = PREROLL_NONE;

    if( unlikely(prerolled) )
    {
        msg_Dbg( p_dec, "end of audio preroll" );

        vlc_aout_stream_Flush( p_astream );
    }

    int ret = DecoderWaitUnblock(p_owner);
    if (ret != VLC_SUCCESS)
    {
        block_Release(p_audio);
        return ret;
    }

    int status = vlc_aout_stream_Play( p_astream, p_audio );
    if( status == AOUT_DEC_CHANGED )
    {
        /* Only reload the decoder */
        RequestReload( p_owner );
    }
    else if( status == AOUT_DEC_FAILED )
    {
        /* If we reload because the aout failed, we should release it. That
            * way, a next call to ModuleThread_UpdateAudioFormat() won't re-use the
            * previous (failing) aout but will try to create a new one. */
        atomic_store( &p_owner->reload, RELOAD_DECODER_AOUT );
    }

    return VLC_SUCCESS;
}

static void ModuleThread_QueueAudio( decoder_t *p_dec, vlc_frame_t *p_aout_buf )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );

    if ( tracer != NULL && p_aout_buf != NULL )
    {
        vlc_tracer_TraceStreamDTS( tracer, "DEC", p_owner->psz_id, "OUT",
                            p_aout_buf->i_pts, p_aout_buf->i_dts );
    }

    vlc_fifo_Lock(p_owner->p_fifo);

    int success = ModuleThread_PlayAudio( p_owner, p_aout_buf );

    unsigned played = 0;
    unsigned aout_lost = 0;
    if( p_owner->p_astream != NULL )
    {
        vlc_aout_stream_GetResetStats( p_owner->p_astream, &aout_lost, &played );
    }
    if (success != VLC_SUCCESS)
        aout_lost++;

    vlc_fifo_Unlock(p_owner->p_fifo);

    decoder_Notify(p_owner, on_new_audio_stats, 1, aout_lost, played);
}

static void ModuleThread_PlaySpu( vlc_input_decoder_t *p_owner, subpicture_t *p_subpic )
{
    decoder_t *p_dec = &p_owner->dec;
    vout_thread_t *p_vout = p_owner->p_vout;

    /* */
    if( p_subpic->i_start == VLC_TICK_INVALID )
    {
        msg_Warn( p_dec, "non-dated spu buffer received" );
        subpicture_Delete( p_subpic );
        return;
    }

    /* */
    int ret = DecoderWaitUnblock(p_owner);

    if (ret != VLC_SUCCESS || p_subpic->i_start == VLC_TICK_INVALID)
    {
        subpicture_Delete( p_subpic );
        return;
    }

    vout_PutSubpicture( p_vout, p_subpic );
}

static void ModuleThread_QueueSpu( decoder_t *p_dec, subpicture_t *p_spu )
{
    assert( p_spu );
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );

    if ( tracer != NULL && p_spu != NULL )
    {
        vlc_tracer_TraceStreamPTS( tracer, "DEC", p_owner->psz_id,
                            "OUT", p_spu->i_start );
    }

    /* The vout must be created from a previous decoder_NewSubpicture call. */
    assert( p_owner->p_vout );

    /* Preroll does not work very well with subtitle */
    vlc_fifo_Lock(p_owner->p_fifo);
    if( p_spu->i_start != VLC_TICK_INVALID &&
        p_spu->i_start < p_owner->i_preroll_end &&
        ( p_spu->i_stop == VLC_TICK_INVALID || p_spu->i_stop < p_owner->i_preroll_end ) )
        subpicture_Delete( p_spu );
    else
        ModuleThread_PlaySpu( p_owner, p_spu );
    vlc_fifo_Unlock(p_owner->p_fifo);
}

static void DecoderThread_ProcessInput( vlc_input_decoder_t *p_owner, vlc_frame_t *frame );
static void DecoderThread_DecodeBlock( vlc_input_decoder_t *p_owner, vlc_frame_t *frame )
{
    decoder_t *p_dec = &p_owner->dec;
    struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );

    vlc_fifo_Unlock(p_owner->p_fifo);

    if ( tracer != NULL && frame != NULL )
    {
        vlc_tracer_TraceStreamDTS( tracer, "DEC", p_owner->psz_id, "IN",
                            frame->i_pts, frame->i_dts );
    }

    int ret = p_dec->pf_decode( p_dec, frame );

    vlc_fifo_Lock(p_owner->p_fifo);
    switch( ret )
    {
        case VLCDEC_SUCCESS:
            break;
        case VLCDEC_ECRITICAL:
            p_owner->error = true;
            break;
        case VLCDEC_RELOAD:
            RequestReload( p_owner );
            if( unlikely( frame == NULL ) )
                break;
            if( !( frame->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
            {
                frame->i_flags |= BLOCK_FLAG_CORE_PRIVATE_RELOADED;
                DecoderThread_ProcessInput( p_owner, frame );
            }
            else /* We prefer loosing this frame than an infinite recursion */
                block_Release( frame );
            break;
        default:
            vlc_assert_unreachable();
    }
}

/**
 * Decode a frame
 *
 * \param p_owner the input decoder object
 * \param frame the block to decode
 */
static void DecoderThread_ProcessInput( vlc_input_decoder_t *p_owner, vlc_frame_t *frame )
{
    decoder_t *p_dec = &p_owner->dec;

    if( p_owner->error )
        goto error;

    /* Here, the atomic doesn't prevent to miss a reload request.
     * DecoderThread_ProcessInput() can still be called after the decoder module or the
     * audio output requested a reload. This will only result in a drop of an
     * input frame or an output buffer. */
    enum reload reload;
    if( ( reload = atomic_exchange( &p_owner->reload, RELOAD_NO_REQUEST ) ) )
    {
        msg_Warn( p_dec, "Reloading the decoder module%s",
                  reload == RELOAD_DECODER_AOUT ? " and the audio output" : "" );

        if( DecoderThread_Reload( p_owner, p_dec->fmt_in, reload ) != VLC_SUCCESS )
            goto error;
    }

    bool packetize = p_owner->p_packetizer != NULL;
    if( frame )
    {
        if( frame->i_buffer <= 0 )
            goto error;

        DecoderUpdatePreroll( &p_owner->i_preroll_end, frame );
        if( unlikely( frame->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
        {
            /* This frame has already been packetized */
            packetize = false;
        }
    }

    if( p_owner->p_sout != NULL )
    {
        vlc_fifo_Unlock(p_owner->p_fifo);
        DecoderThread_ProcessSout( p_owner, frame );
        vlc_fifo_Lock(p_owner->p_fifo);
        return;
    }
    if( packetize )
    {
        vlc_frame_t *packetized_frame;
        vlc_frame_t **ppframe = frame ? &frame : NULL;
        decoder_t *p_packetizer = p_owner->p_packetizer;

        while( (packetized_frame =
                p_packetizer->pf_packetize( p_packetizer, ppframe ) ) )
        {
            if( !es_format_IsSimilar( p_dec->fmt_in, &p_packetizer->fmt_out ) )
            {
                msg_Dbg( p_dec, "restarting module due to input format change");
                es_format_LogDifferences( vlc_object_logger(p_dec),
                                          "decoder in", p_dec->fmt_in,
                                          "packetizer out", &p_packetizer->fmt_out );

                /* Drain the decoder module */
                DecoderThread_DecodeBlock( p_owner, NULL );

                if( DecoderThread_Reload( p_owner, &p_packetizer->fmt_out,
                                          RELOAD_DECODER ) != VLC_SUCCESS )
                {
                    block_ChainRelease( packetized_frame );
                    return;
                }
            }

            if( p_packetizer->pf_get_cc )
                PacketizerGetCc( p_owner, p_packetizer );

            while( packetized_frame )
            {
                vlc_frame_t *p_next = packetized_frame->p_next;
                packetized_frame->p_next = NULL;

                DecoderThread_DecodeBlock( p_owner, packetized_frame );

                if( p_owner->error )
                {
                    block_ChainRelease( p_next );
                    return;
                }

                packetized_frame = p_next;
            }
        }
        /* Drain the decoder after the packetizer is drained */
        if( !ppframe )
            DecoderThread_DecodeBlock( p_owner, NULL );
    }
    else
        DecoderThread_DecodeBlock( p_owner, frame );
    return;

error:
    if( frame )
        block_Release( frame );
}

static void DecoderThread_Flush( vlc_input_decoder_t *p_owner )
{
    decoder_t *p_dec = &p_owner->dec;
    decoder_t *p_packetizer = p_owner->p_packetizer;

    if( p_owner->error )
        return;

    if( p_packetizer != NULL && p_packetizer->pf_flush != NULL )
        p_packetizer->pf_flush( p_packetizer );

    if ( p_dec->pf_flush != NULL )
        p_dec->pf_flush( p_dec );
}

/**
 * The decoding main loop
 *
 * \param p_data the input decoder object
 */
static void *DecoderThread( void *p_data )
{
    vlc_input_decoder_t *p_owner = (vlc_input_decoder_t *)p_data;

    const char *thread_name;
    switch (p_owner->dec.fmt_in->i_cat)
    {
        case VIDEO_ES: thread_name = "vlc-dec-video"; break;
        case AUDIO_ES: thread_name = "vlc-dec-audio"; break;
        case SPU_ES:   thread_name = "vlc-dec-spu"; break;
        case DATA_ES:  thread_name = "vlc-dec-data"; break;
        default:       thread_name = "vlc-decoder"; break;
    }

    vlc_thread_set_name(thread_name);

    /* The decoder's main loop */
    vlc_fifo_Lock( p_owner->p_fifo );

    while( !p_owner->aborting || p_owner->flushing )
    {
        if( p_owner->flushing )
        {   /* Flush before/regardless of pause. We do not want to resume just
             * for the sake of flushing (glitches could otherwise happen). */
            vlc_fifo_Unlock( p_owner->p_fifo );

            /* Flush the decoder (and the output) */
            DecoderThread_Flush( p_owner );

            vlc_fifo_Lock( p_owner->p_fifo );

            /* Reset flushing after DecoderThread_ProcessInput in case vlc_input_decoder_Flush
             * is called again. This will avoid a second useless flush (but
             * harmless). */
            p_owner->flushing = false;
            p_owner->i_preroll_end = PREROLL_NONE;
            continue;
        }

        if( p_owner->paused != p_owner->output_paused )
        {   /* Update playing/paused status of the output */
            Decoder_ChangeOutputPause( p_owner, p_owner->paused, p_owner->pause_date );
            continue;
        }

        if( p_owner->rate != p_owner->output_rate )
        {
            Decoder_ChangeOutputRate( p_owner, p_owner->rate );
            continue;
        }

        if( p_owner->delay != p_owner->output_delay )
        {
            Decoder_ChangeOutputDelay( p_owner, p_owner->delay );
            continue;
        }

        if( p_owner->paused && p_owner->frames_countdown == 0 )
        {   /* Wait for resumption from pause */
            p_owner->b_idle = true;
            vlc_cond_signal( &p_owner->wait_acknowledge );
            vlc_fifo_Wait( p_owner->p_fifo );
            p_owner->b_idle = false;
            continue;
        }

        vlc_cond_signal( &p_owner->wait_fifo );

        vlc_frame_t *frame = vlc_fifo_DequeueUnlocked( p_owner->p_fifo );
        if( frame == NULL )
        {
            if( likely(!p_owner->b_draining) )
            {   /* Wait for a block to decode (or a request to drain) */
                p_owner->b_idle = true;
                vlc_cond_signal( &p_owner->wait_acknowledge );
                vlc_fifo_Wait( p_owner->p_fifo );
                p_owner->b_idle = false;
                continue;
            }
            /* We have emptied the FIFO and there is a pending request to
             * drain. Pass frame = NULL to decoder just once. */
        }

        /* DecoderThread_ProcessInput will unlock when playing to the decoders
         * but will ensure it re-locks in the end. This is necessary to handle
         * reloading, CC and packetizing. */
        DecoderThread_ProcessInput( p_owner, frame );

        if( p_owner->b_draining && frame == NULL )
        {
            p_owner->b_draining = false;

            if( p_owner->dec.fmt_in->i_cat == AUDIO_ES && p_owner->p_astream != NULL )
            {   /* Draining: the decoder is drained and all decoded buffers are
                 * queued to the output at this point. Now drain the output. */
                vlc_aout_stream_Drain( p_owner->p_astream );
            }
        }

        vlc_cond_signal( &p_owner->wait_acknowledge );
    }

    vlc_fifo_Unlock( p_owner->p_fifo );
    return NULL;
}

static const struct decoder_owner_callbacks dec_video_cbs =
{
    .video = {
        .get_device = ModuleThread_GetDecoderDevice,
        .format_update = ModuleThread_UpdateVideoFormat,
        .buffer_new = ModuleThread_NewVideoBuffer,
        .queue = ModuleThread_QueueVideo,
        .queue_cc = ModuleThread_QueueCc,
        .get_display_date = ModuleThread_GetDisplayDate,
        .get_display_rate = ModuleThread_GetDisplayRate,
    },
    .get_attachments = InputThread_GetInputAttachments,
};
static const struct decoder_owner_callbacks dec_thumbnailer_cbs =
{
    .video = {
        .get_device = thumbnailer_get_device,
        .buffer_new = thumbnailer_buffer_new,
        .queue = ModuleThread_QueueThumbnail,
    },
    .get_attachments = InputThread_GetInputAttachments,
};
static const struct decoder_owner_callbacks dec_audio_cbs =
{
    .audio = {
        .format_update = ModuleThread_UpdateAudioFormat,
        .queue = ModuleThread_QueueAudio,
    },
    .get_attachments = InputThread_GetInputAttachments,
};
static const struct decoder_owner_callbacks dec_spu_cbs =
{
    .spu = {
        .buffer_new = ModuleThread_NewSpuBuffer,
        .queue = ModuleThread_QueueSpu,
    },
    .get_attachments = InputThread_GetInputAttachments,
};

/**
 * Create a decoder object
 *
 * \param p_parent a VLC parent object to inherit variable from
 * \param cfg the input decoder configuration
 * \return a new input decoder object
 */
static vlc_input_decoder_t *
CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
{
    decoder_t *p_dec;
    vlc_input_decoder_t *p_owner;
    static_assert(offsetof(vlc_input_decoder_t, dec) == 0,
                  "the decoder must be first in the owner structure");

    assert(cfg->input_type != INPUT_TYPE_PREPARSING);

    const es_format_t *fmt = cfg->fmt;

    p_owner = vlc_custom_create( p_parent, sizeof( *p_owner ), "decoder" );
    if( p_owner == NULL )
        return NULL;
    p_dec = &p_owner->dec;

    p_owner->psz_id = cfg->str_id;
    p_owner->p_clock = cfg->clock;
    p_owner->i_preroll_end = PREROLL_NONE;
    p_owner->p_resource = cfg->resource;
    p_owner->hw_dec = cfg->hw_dec;
    p_owner->cbs = cfg->cbs;
    p_owner->cbs_userdata = cfg->cbs_data;
    p_owner->p_aout = NULL;
    p_owner->p_astream = NULL;
    p_owner->p_vout = NULL;
    p_owner->vout_started = false;
    p_owner->i_spu_channel = VOUT_SPU_CHANNEL_INVALID;
    p_owner->i_spu_order = 0;
    p_owner->p_sout = cfg->sout;
    p_owner->p_sout_input = NULL;
    p_owner->p_packetizer = NULL;

    p_owner->b_fmt_description = false;
    p_owner->p_description = NULL;

    p_owner->output_delay = p_owner->delay = 0;
    p_owner->output_rate = p_owner->rate = 1.f;
    p_owner->output_paused = p_owner->paused = false;
    p_owner->pause_date = VLC_TICK_INVALID;
    p_owner->frames_countdown = 0;

    p_owner->b_waiting = false;
    p_owner->b_first = true;
    p_owner->b_has_data = false;

    p_owner->error = false;

    p_owner->flushing = false;
    p_owner->b_draining = false;
    atomic_init( &p_owner->reload, RELOAD_NO_REQUEST );
    p_owner->b_idle = false;

    p_owner->mouse_event = NULL;
    p_owner->mouse_opaque = NULL;

    es_format_Init( &p_owner->fmt, fmt->i_cat, 0 );

    /* decoder fifo */
    p_owner->p_fifo = block_FifoNew();
    if( unlikely(p_owner->p_fifo == NULL) )
    {
        vlc_object_delete(p_dec);
        return NULL;
    }

    vlc_mutex_init( &p_owner->mouse_lock );
    vlc_cond_init( &p_owner->wait_request );
    vlc_cond_init( &p_owner->wait_acknowledge );
    vlc_cond_init( &p_owner->wait_fifo );

    /* Load a packetizer module if the input is not already packetized */
    if( cfg->sout == NULL && !fmt->b_packetized )
    {
        p_owner->p_packetizer =
            vlc_custom_create( p_parent, sizeof( decoder_t ), "packetizer" );
        if( p_owner->p_packetizer )
        {
            decoder_Init(p_owner->p_packetizer, &p_owner->pktz_fmt_in, fmt);
            if (LoadDecoder(p_owner->p_packetizer, true, &p_owner->pktz_fmt_in))
            {
                vlc_object_delete(p_owner->p_packetizer);
                p_owner->p_packetizer = NULL;
            }
            else
            {
                p_owner->p_packetizer->fmt_out.b_packetized = true;
                fmt = &p_owner->p_packetizer->fmt_out;
            }
        }
    }

    switch( fmt->i_cat )
    {
        case VIDEO_ES:
            if( cfg->input_type == INPUT_TYPE_THUMBNAILING )
                p_dec->cbs = &dec_thumbnailer_cbs;
            else
                p_dec->cbs = &dec_video_cbs;
            break;
        case AUDIO_ES:
            p_dec->cbs = &dec_audio_cbs;
            break;
        case SPU_ES:
            p_dec->cbs = &dec_spu_cbs;
            break;
        default:
            msg_Err( p_dec, "unknown ES format" );
            return p_owner;
    }

    /* Find a suitable decoder/packetizer module */
    decoder_Init(p_dec, &p_owner->dec_fmt_in, fmt);
    if (LoadDecoder(p_dec, cfg->sout != NULL, &p_owner->dec_fmt_in))
        return p_owner;

    assert( p_dec->fmt_in->i_cat == p_dec->fmt_out.i_cat && fmt->i_cat == p_dec->fmt_in->i_cat);

    /* Copy ourself the input replay gain */
    if( fmt->i_cat == AUDIO_ES )
    {
        for( unsigned i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
        {
            if( !p_dec->fmt_out.audio_replay_gain.pb_peak[i] )
            {
                p_dec->fmt_out.audio_replay_gain.pb_peak[i] = fmt->audio_replay_gain.pb_peak[i];
                p_dec->fmt_out.audio_replay_gain.pf_peak[i] = fmt->audio_replay_gain.pf_peak[i];
            }
            if( !p_dec->fmt_out.audio_replay_gain.pb_gain[i] )
            {
                p_dec->fmt_out.audio_replay_gain.pb_gain[i] = fmt->audio_replay_gain.pb_gain[i];
                p_dec->fmt_out.audio_replay_gain.pf_gain[i] = fmt->audio_replay_gain.pf_gain[i];
            }
        }
    }

    /* */
    vlc_mutex_init(&p_owner->subdecs.lock);
    p_owner->cc.selected_codec = cfg->cc_decoder == 708 ?
                                 VLC_CODEC_CEA708 : VLC_CODEC_CEA608;
    p_owner->cc.b_supported = ( cfg->sout == NULL );

    p_owner->cc.desc.i_608_channels = 0;
    p_owner->cc.desc.i_708_channels = 0;
    vlc_list_init(&p_owner->subdecs.list);
    p_owner->cc.count = 0;
    p_owner->cc.p_sout_input = NULL;
    p_owner->cc.sout_es_id = NULL;
    p_owner->cc.b_sout_created = false;
    p_owner->master_dec = NULL;
    return p_owner;
}

/**
 * Destroys a decoder object
 *
 * \param p_owner the input decoder object
 * \param i_cat the elementary stream format category for the decoder
 */
static void DeleteDecoder( vlc_input_decoder_t *p_owner, enum es_format_category_e i_cat )
{
    decoder_t *p_dec = &p_owner->dec;
    msg_Dbg( p_dec, "killing decoder fourcc `%4.4s'",
             (char*)&p_dec->fmt_in->i_codec );

    decoder_Clean( p_dec );

    if ( p_owner->out_pool )
    {
        picture_pool_Release( p_owner->out_pool );
        p_owner->out_pool = NULL;
    }

    if (p_owner->vctx)
        vlc_video_context_Release( p_owner->vctx );

    /* Free all packets still in the decoder fifo. */
    block_FifoEmpty( p_owner->p_fifo );

    /* Cleanup */
    if( p_owner->p_sout_input )
    {
        sout_InputDelete( p_owner->p_sout, p_owner->p_sout_input );
        if( p_owner->cc.p_sout_input )
            sout_InputDelete( p_owner->p_sout, p_owner->cc.p_sout_input );
        free( p_owner->cc.sout_es_id );
    }

    switch( i_cat )
    {
        case AUDIO_ES:
            if( p_owner->p_aout )
            {
                /* TODO: REVISIT gap-less audio */
                assert( p_owner->p_astream );
                vlc_aout_stream_Delete( p_owner->p_astream );
                input_resource_PutAout( p_owner->p_resource, p_owner->p_aout );
            }
            break;
        case VIDEO_ES: {
            vout_thread_t *vout = p_owner->p_vout;

            if (vout != NULL)
            {
                /* Hold the vout since PutVout will likely release it and a
                 * last reference is needed for notify callbacks */
                vout_Hold(vout);

                enum input_resource_vout_state vout_state;
                input_resource_PutVout(p_owner->p_resource, vout, &vout_state);
                if (vout_state == INPUT_RESOURCE_VOUT_STOPPED)
                    decoder_Notify(p_owner, on_vout_stopped, vout);

                vout_Release(vout);
            }
            break;
        }
        case SPU_ES:
        {
            if( p_owner->p_vout )
            {
                assert( p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID );
                decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);

                vout_UnregisterSubpictureChannel( p_owner->p_vout,
                                                  p_owner->i_spu_channel );
                vout_Release(p_owner->p_vout);
            }
            break;
        }
        case DATA_ES:
        case UNKNOWN_ES:
            break;
        default:
            vlc_assert_unreachable();
    }

    es_format_Clean( &p_owner->dec_fmt_in );
    es_format_Clean( &p_owner->pktz_fmt_in );
    es_format_Clean( &p_owner->fmt );

    if( p_owner->p_description )
        vlc_meta_Delete( p_owner->p_description );

    block_FifoRelease( p_owner->p_fifo );
    decoder_Destroy( p_owner->p_packetizer );
    decoder_Destroy( &p_owner->dec );
}

/* */
static void DecoderUnsupportedCodec( decoder_t *p_dec, const es_format_t *fmt, bool b_decoding )
{
    if (fmt->i_codec != VLC_CODEC_UNKNOWN && fmt->i_codec) {
        const char *desc = vlc_fourcc_GetDescription(fmt->i_cat, fmt->i_codec);
        if (!desc || !*desc)
            desc = N_("No description for this codec");
        msg_Err( p_dec, "Codec `%4.4s' (%s) is not supported.", (char*)&fmt->i_codec, desc );
        vlc_dialog_display_error( p_dec, _("Codec not supported"),
            _("VLC could not decode the format \"%4.4s\" (%s)"),
            (char*)&fmt->i_codec, desc );
    } else if( b_decoding ){
        msg_Err( p_dec, "could not identify codec" );
        vlc_dialog_display_error( p_dec, _("Unidentified codec"),
            _("VLC could not identify the audio or video codec" ) );
    }
}

/* TODO: pass p_sout through p_resource? -- Courmisch */
static vlc_input_decoder_t *
decoder_New( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
{
    const char *psz_type = cfg->sout ? N_("packetizer") : N_("decoder");

    /* Create the decoder configuration structure */
    vlc_input_decoder_t *p_owner = CreateDecoder( p_parent, cfg );
    if( p_owner == NULL )
    {
        msg_Err( p_parent, "could not create %s", cfg->str_id );
        vlc_dialog_display_error( p_parent, _("Streaming / Transcoding failed"),
            _("VLC could not open the %s module."), vlc_gettext( psz_type ) );
        return NULL;
    }

    decoder_t *p_dec = &p_owner->dec;
    if( !p_dec->p_module )
    {
        DecoderUnsupportedCodec( p_dec, cfg->fmt, !cfg->sout );

        /* Don't use dec->fmt_in->i_cat since it may not be initialized here. */
        DeleteDecoder( p_owner, cfg->fmt->i_cat );
        return NULL;
    }

    assert( p_dec->fmt_in->i_cat != UNKNOWN_ES );

    /* Do not delay sout creation for SPU or DATA. */
    if( cfg->sout && cfg->fmt->b_packetized &&
        (cfg->fmt->i_cat != VIDEO_ES && cfg->fmt->i_cat != AUDIO_ES) )
    {
        p_owner->p_sout_input = sout_InputNew( p_owner->p_sout, cfg->fmt, p_owner->psz_id );
        if( p_owner->p_sout_input == NULL )
        {
            msg_Err( p_dec, "cannot create sout input (%4.4s)",
                     (char *)&cfg->fmt->i_codec );
            p_owner->error = true;
        }
    }

    if( !vlc_input_decoder_IsSynchronous( p_owner ) )
    {
        /* Spawn the decoder thread in asynchronous scenario. */
        if( vlc_clone( &p_owner->thread, DecoderThread, p_owner ) )
        {
            msg_Err( p_dec, "cannot spawn decoder thread" );
            DeleteDecoder( p_owner, p_dec->fmt_in->i_cat );
            return NULL;
        }
    }

    return p_owner;
}


/**
 * Spawns a new decoder thread from the input thread
 *
 * \param parent the VLC object to inherit variable from
 * \param cfg the input decoder configuration
 * \return the spawned decoder object
 */
vlc_input_decoder_t *
vlc_input_decoder_New( vlc_object_t *parent, const struct vlc_input_decoder_cfg *cfg )
{
    return decoder_New( parent, cfg );
}

vlc_input_decoder_t *
vlc_input_decoder_Create( vlc_object_t *p_parent, const es_format_t *fmt, const char *es_id,
                          struct vlc_clock_t *clock, input_resource_t *p_resource )
{
    const struct vlc_input_decoder_cfg cfg = {
        .fmt = fmt,
        .str_id = es_id,
        .clock = clock,
        .resource = p_resource,
        .sout = NULL,
        .input_type = INPUT_TYPE_PLAYBACK,
        .hw_dec = var_InheritBool( p_parent, "hw-dec" ),
        .cbs = NULL, .cbs_data = NULL,
    };
    return decoder_New( p_parent, &cfg );
}

static void RemoveCcDecoder(vlc_input_decoder_t *owner,
                            vlc_input_decoder_t *subdec)
{
    vlc_mutex_lock(&owner->subdecs.lock);

    vlc_input_decoder_t *it;
    vlc_list_foreach(it, &owner->subdecs.list, node)
        if (it == subdec)
        {
            vlc_list_remove(&it->node);
            owner->cc.count--;
            vlc_mutex_unlock(&owner->subdecs.lock);
            return;
        }

    vlc_assert_unreachable();
}

void vlc_input_decoder_Delete( vlc_input_decoder_t *p_owner )
{
    if (p_owner->master_dec != NULL)
        RemoveCcDecoder(p_owner->master_dec, p_owner);

    vlc_fifo_Lock( p_owner->p_fifo );
    p_owner->aborting = true;
    p_owner->b_waiting = false;
    vlc_fifo_Signal( p_owner->p_fifo );

    /* Make sure we aren't waiting/decoding anymore */
    vlc_cond_signal( &p_owner->wait_request );
    vlc_fifo_Unlock( p_owner->p_fifo );

    if( !vlc_input_decoder_IsSynchronous( p_owner ) )
        vlc_join( p_owner->thread, NULL );

#ifndef NDEBUG
    vlc_mutex_lock(&p_owner->subdecs.lock);
    assert(vlc_list_is_empty(&p_owner->subdecs.list));
    assert(p_owner->cc.count == 0);
    vlc_mutex_unlock(&p_owner->subdecs.lock);
#endif

    /* Delete decoder */
    DeleteDecoder(p_owner, p_owner->dec_fmt_in.i_cat);
}

void vlc_subdec_desc_Clean(struct vlc_subdec_desc *desc)
{
    for (size_t i = 0; i < desc->fmt_count; ++i)
        es_format_Clean(&desc->fmt_array[i]);
    free(desc->fmt_array);
}

static void GetCCDescLocked(vlc_input_decoder_t *owner,
                            struct vlc_subdec_desc *desc)
{
    vlc_fifo_Assert(owner->p_fifo);

    if (!owner->cc.desc_changed)
    {
        desc->fmt_array = NULL;
        desc->fmt_count = 0;
        return;
    }
    owner->cc.desc_changed = false;

    size_t max_channels;
    uint64_t bitmap;
    GetCcChannels(owner, &max_channels, &bitmap);

    int count = vlc_popcount(bitmap);
    if (count == 0)
    {
        desc->fmt_array = NULL;
        desc->fmt_count = 0;
        return;
    }

    desc->fmt_array = vlc_alloc(count, sizeof(es_format_t));
    if (unlikely(desc->fmt_array == NULL))
    {
        desc->fmt_count = 0;
        return;
    }
    desc->fmt_count = count;

    static const char CEA608_fmtdesc[] = N_("Closed captions %u");
    static const char CEA708_fmtdesc[] = N_("DTVCC Closed captions %u");
    const char *fmtdesc = owner->cc.selected_codec == VLC_CODEC_CEA608 ?
                          CEA608_fmtdesc : CEA708_fmtdesc;

    size_t array_idx = 0;
    for (int i = 0; bitmap > 0; bitmap >>= 1, i++)
    {
        if ((bitmap & 1) == 0)
            continue;
        es_format_t *fmt = &desc->fmt_array[array_idx++];
        es_format_Init(fmt, SPU_ES, owner->cc.selected_codec);
        fmt->i_id = i + 1;
        fmt->subs.cc.i_channel = i;
        fmt->subs.cc.i_reorder_depth = owner->cc.desc.i_reorder_depth;
        if (asprintf(&fmt->psz_description, fmtdesc, fmt->i_id) == -1)
            fmt->psz_description = NULL;
    }
}

static void GetStatusLocked(vlc_input_decoder_t *p_owner,
                            struct vlc_input_decoder_status *status)
{
    vlc_fifo_Assert(p_owner->p_fifo);

    status->format.changed = p_owner->b_fmt_description;
    p_owner->b_fmt_description = false;

    if( status->format.changed && p_owner->fmt.i_cat == UNKNOWN_ES )
    {
        /* The format changed but the output creation failed */
        status->format.changed = false;
    }

    if( status->format.changed )
    {
        es_format_Copy( &status->format.fmt, &p_owner->fmt );
        status->format.meta = NULL;

        if( p_owner->p_description )
        {
            status->format.meta  = vlc_meta_New();
            if( status->format.meta  )
                vlc_meta_Merge( status->format.meta, p_owner->p_description );
        }
    }
    GetCCDescLocked(p_owner, &status->subdec_desc);
}

void vlc_input_decoder_DecodeWithStatus(vlc_input_decoder_t *p_owner, vlc_frame_t *frame,
                                        bool b_do_pace,
                                        struct vlc_input_decoder_status *status)
{
    if( vlc_input_decoder_IsSynchronous( p_owner ) )
    {
        /* DecoderThread's fifo should be empty as no decoder thread is running. */
        assert( vlc_fifo_IsEmpty( p_owner->p_fifo ) );
        vlc_fifo_Lock(p_owner->p_fifo);
        DecoderThread_ProcessInput( p_owner, frame );
        if (status != NULL)
            GetStatusLocked(p_owner, status);
        vlc_fifo_Unlock(p_owner->p_fifo);
        return;
    }

    vlc_fifo_Lock( p_owner->p_fifo );
    if( !b_do_pace )
    {
        /* FIXME: ideally we would check the time amount of data
         * in the FIFO instead of its size. */
        /* 400 MiB, i.e. ~ 50mb/s for 60s */
        if( vlc_fifo_GetBytes( p_owner->p_fifo ) > 400*1024*1024 )
        {
            msg_Warn( &p_owner->dec, "decoder/packetizer fifo full (data not "
                      "consumed quickly enough), resetting fifo!" );
            block_ChainRelease( vlc_fifo_DequeueAllUnlocked( p_owner->p_fifo ) );
            frame->i_flags |= BLOCK_FLAG_DISCONTINUITY;
        }
    }
    else
    if( !p_owner->b_waiting )
    {   /* The FIFO is not consumed when waiting, so pacing would deadlock VLC.
         * Locking is not necessary as b_waiting is only read, not written by
         * the decoder thread. */
        while( vlc_fifo_GetCount( p_owner->p_fifo ) >= 10 )
            vlc_fifo_WaitCond( p_owner->p_fifo, &p_owner->wait_fifo );
    }

    vlc_fifo_QueueUnlocked( p_owner->p_fifo, frame );
    if (status != NULL)
        GetStatusLocked(p_owner, status);
    vlc_fifo_Unlock( p_owner->p_fifo );
}

void vlc_input_decoder_Decode(vlc_input_decoder_t *p_owner, vlc_frame_t *frame,
                              bool b_do_pace)
{
    vlc_input_decoder_DecodeWithStatus(p_owner, frame, b_do_pace, NULL);
}

bool vlc_input_decoder_IsEmpty( vlc_input_decoder_t * p_owner )
{
    assert( !p_owner->b_waiting );

    vlc_fifo_Lock( p_owner->p_fifo );
    if( !vlc_fifo_IsEmpty( p_owner->p_fifo ) || p_owner->b_draining )
    {
        vlc_fifo_Unlock( p_owner->p_fifo );
        return false;
    }

    bool b_empty;

    if( p_owner->p_sout_input != NULL )
        b_empty = true;
    else if( p_owner->fmt.i_cat == VIDEO_ES && p_owner->p_vout != NULL )
        b_empty = vout_IsEmpty( p_owner->p_vout );
    else if( p_owner->fmt.i_cat == AUDIO_ES && p_owner->p_astream != NULL )
        b_empty = vlc_aout_stream_IsDrained( p_owner->p_astream );
    else
        b_empty = true; /* TODO subtitles support */
    vlc_fifo_Unlock( p_owner->p_fifo );

    return b_empty;
}

void vlc_input_decoder_Drain( vlc_input_decoder_t *p_owner )
{
    if ( vlc_input_decoder_IsSynchronous( p_owner ) )
    {
        /* Process a NULL frame synchronously to signal draining to packetizer/decoder. */
        vlc_fifo_Lock(p_owner->p_fifo);
        DecoderThread_ProcessInput( p_owner, NULL );
        vlc_fifo_Unlock(p_owner->p_fifo);
        return;
    }

    vlc_fifo_Lock( p_owner->p_fifo );
    p_owner->b_draining = true;
    vlc_fifo_Signal( p_owner->p_fifo );
    vlc_fifo_Unlock( p_owner->p_fifo );
}

void vlc_input_decoder_Flush( vlc_input_decoder_t *p_owner )
{
    vlc_fifo_Lock( p_owner->p_fifo );
    enum es_format_category_e cat = p_owner->dec.fmt_in->i_cat;

    /* Empty the fifo */
    block_ChainRelease( vlc_fifo_DequeueAllUnlocked( p_owner->p_fifo ) );

    /* Don't need to wait for the DecoderThread to flush. Indeed, if called a
     * second time, this function will clear the FIFO again before anything was
     * dequeued by DecoderThread and there is no need to flush a second time in
     * a row. */
    p_owner->flushing = true;
    p_owner->b_draining = false;

    /* Flush video/spu decoder when paused: increment frames_countdown in order
     * to display one frame/subtitle */
    if( p_owner->paused && ( cat == VIDEO_ES || cat == SPU_ES )
     && p_owner->frames_countdown == 0 )
        p_owner->frames_countdown++;

    if ( p_owner->p_sout_input != NULL )
    {
        sout_InputFlush( p_owner->p_sout, p_owner->p_sout_input );
    }
    else if( cat == AUDIO_ES )
    {
        if( p_owner->p_astream )
            vlc_aout_stream_Flush( p_owner->p_astream );
    }
    else if( cat == VIDEO_ES )
    {
        if( p_owner->p_vout && p_owner->vout_started )
            vout_FlushAll( p_owner->p_vout );
    }
    else if( cat == SPU_ES )
    {
        if( p_owner->p_vout )
        {
            assert( p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID );
            vout_FlushSubpictureChannel( p_owner->p_vout, p_owner->i_spu_channel );
        }
    }
    vlc_fifo_Signal( p_owner->p_fifo );

    if (unlikely(p_owner->b_waiting && p_owner->b_has_data))
    {
        /* Signal the output thread to stop waiting from DecoderWaitUnblock()
         * and to discard the current frame (via 'flushing' = true). */
        vlc_cond_signal(&p_owner->wait_request);

        /* Flushing is fully asynchronous, but we need to wait for the output
         * thread to unblock in DecoderWaitUnblock() otherwise there are no
         * ways to know if the frame referenced when waiting comes from before
         * or after the flush. Waiting here is almost instantaneous since we
         * are sure that the output thread is waiting in DecoderWaitUnblock().
         */
        while (p_owner->b_has_data)
            vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_acknowledge);
    }

    vlc_fifo_Unlock( p_owner->p_fifo );

    if (vlc_input_decoder_IsSynchronous(p_owner))
    {
        /* With a synchronous decoder,there is no decoder thread which
         * can process the flush request. We flush synchronously from
         * here and reset the flushing state. */
        DecoderThread_Flush(p_owner);
        vlc_fifo_Lock(p_owner->p_fifo);
        p_owner->flushing = false;
        p_owner->i_preroll_end = PREROLL_NONE;
        vlc_fifo_Unlock(p_owner->p_fifo);
    }
}

vlc_input_decoder_t *
vlc_input_decoder_CreateSubDec(vlc_input_decoder_t *p_owner,
                               const struct vlc_input_decoder_cfg *cfg)
{
    decoder_t *p_dec = &p_owner->dec;

    vlc_mutex_lock(&p_owner->subdecs.lock);

    vlc_input_decoder_t *p_ccowner;

    p_ccowner = vlc_input_decoder_New( VLC_OBJECT(p_dec), cfg );
    if( !p_ccowner )
    {
        msg_Err( p_dec, "could not create decoder" );
        vlc_dialog_display_error( p_dec,
            _("Streaming / Transcoding failed"), "%s",
            _("VLC could not open the decoder module.") );
        vlc_mutex_unlock(&p_owner->subdecs.lock);
        return NULL;
    }
    assert(p_ccowner->dec.p_module != NULL);

    vlc_list_append(&p_ccowner->node, &p_owner->subdecs.list);
    p_owner->cc.count++;
    p_ccowner->master_dec = p_owner;

    vlc_mutex_unlock(&p_owner->subdecs.lock);
    return p_ccowner;
}

void vlc_input_decoder_ChangePause( vlc_input_decoder_t *p_owner,
                                    bool b_paused, vlc_tick_t i_date )
{
    /* Normally, p_owner->b_paused != b_paused here. But if a track is added
     * while the input is paused (e.g. add sub file), then b_paused is
     * (incorrectly) false. FIXME: This is a bug in the decoder owner. */
    vlc_fifo_Lock( p_owner->p_fifo );
    p_owner->paused = b_paused;
    p_owner->pause_date = i_date;
    p_owner->frames_countdown = 0;
    vlc_fifo_Signal( p_owner->p_fifo );
    vlc_fifo_Unlock( p_owner->p_fifo );
}

void vlc_input_decoder_ChangeRate( vlc_input_decoder_t *owner, float rate )
{
    vlc_fifo_Lock( owner->p_fifo );
    owner->rate = rate;
    vlc_fifo_Unlock( owner->p_fifo );
}

void vlc_input_decoder_ChangeDelay( vlc_input_decoder_t *owner, vlc_tick_t delay )
{
    vlc_fifo_Lock( owner->p_fifo );
    owner->delay = delay;
    vlc_fifo_Unlock( owner->p_fifo );
}

void vlc_input_decoder_StartWait( vlc_input_decoder_t *p_owner )
{
    if ( vlc_input_decoder_IsSynchronous( p_owner ) )
        return;

    assert( !p_owner->b_waiting );

    vlc_fifo_Lock(p_owner->p_fifo);
    p_owner->b_has_data = false;
    p_owner->b_first = true;
    p_owner->b_waiting = true;
    vlc_cond_signal(&p_owner->wait_request);
    vlc_fifo_Unlock(p_owner->p_fifo);
}

void vlc_input_decoder_StopWait( vlc_input_decoder_t *p_owner )
{
    if ( vlc_input_decoder_IsSynchronous( p_owner ) )
        return;

    vlc_fifo_Lock(p_owner->p_fifo);
    assert( p_owner->b_waiting );
    p_owner->b_waiting = false;
    vlc_cond_signal( &p_owner->wait_request );
    vlc_fifo_Unlock(p_owner->p_fifo);
}

void vlc_input_decoder_Wait( vlc_input_decoder_t *p_owner )
{
    if ( vlc_input_decoder_IsSynchronous( p_owner ) )
        return;
    assert( p_owner->b_waiting );

    vlc_fifo_Lock(p_owner->p_fifo);
    while( !p_owner->b_has_data )
    {
        /* Don't need to lock p_owner->paused since it's only modified by the
         * owner */
        if( p_owner->paused )
            break;
        if( p_owner->b_idle && vlc_fifo_IsEmpty( p_owner->p_fifo ) )
        {
            msg_Err( &p_owner->dec, "buffer deadlock prevented" );
            break;
        }
        vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_acknowledge);
    }
    vlc_fifo_Unlock(p_owner->p_fifo);
}

void vlc_input_decoder_FrameNext( vlc_input_decoder_t *p_owner )
{
    assert( p_owner->paused );

    vlc_fifo_Lock( p_owner->p_fifo );
    p_owner->frames_countdown++;
    vlc_fifo_Signal( p_owner->p_fifo );
    vlc_fifo_Unlock( p_owner->p_fifo );

    vlc_fifo_Lock(p_owner->p_fifo);
    if( p_owner->dec.fmt_in->i_cat == VIDEO_ES )
    {
        if( p_owner->p_vout )
            vout_NextPicture( p_owner->p_vout );
    }
    vlc_fifo_Unlock(p_owner->p_fifo);
}

size_t vlc_input_decoder_GetFifoSize( vlc_input_decoder_t *p_owner )
{
    return block_FifoSize( p_owner->p_fifo );
}

static bool DecoderHasVbi( decoder_t *dec )
{
    return dec->fmt_in->i_cat == SPU_ES && dec->fmt_in->i_codec == VLC_CODEC_TELETEXT
        && var_Type( dec, "vbi-page" ) == VLC_VAR_INTEGER;
}

int vlc_input_decoder_GetVbiPage( vlc_input_decoder_t *owner, bool *opaque )
{
    decoder_t *dec = &owner->dec;
    if( !DecoderHasVbi( dec ) )
        return -1;
    *opaque = var_GetBool( dec, "vbi-opaque" );
    return var_GetInteger( dec, "vbi-page" );
}

int vlc_input_decoder_SetVbiPage( vlc_input_decoder_t *owner, unsigned page )
{
    decoder_t *dec = &owner->dec;
    if( !DecoderHasVbi( dec ) )
        return VLC_EGENERIC;
    return var_SetInteger( dec, "vbi-page", page );
}

int vlc_input_decoder_SetVbiOpaque( vlc_input_decoder_t *owner, bool opaque )
{
    decoder_t *dec = &owner->dec;
    if( !DecoderHasVbi( dec ) )
        return VLC_EGENERIC;
    return var_SetBool( dec, "vbi-opaque", opaque );
}

void vlc_input_decoder_SetVoutMouseEvent( vlc_input_decoder_t *owner,
                                          vlc_mouse_event mouse_event,
                                          void *user_data )
{
    assert( owner->dec.fmt_in->i_cat == VIDEO_ES );

    vlc_mutex_lock( &owner->mouse_lock );

    owner->mouse_event = mouse_event;
    owner->mouse_opaque = user_data;

    vlc_mutex_unlock( &owner->mouse_lock );
}

int vlc_input_decoder_AddVoutOverlay( vlc_input_decoder_t *owner, subpicture_t *sub,
                                      size_t *channel )
{
    assert( owner->dec.fmt_in->i_cat == VIDEO_ES );
    assert( sub && channel );

    vlc_fifo_Lock(owner->p_fifo);

    if( !owner->p_vout )
    {
        vlc_fifo_Unlock(owner->p_fifo);
        return VLC_EGENERIC;
    }
    ssize_t channel_id =
        vout_RegisterSubpictureChannel( owner->p_vout );
    if (channel_id == -1)
    {
        vlc_fifo_Unlock(owner->p_fifo);
        return VLC_EGENERIC;
    }
    sub->i_start = sub->i_stop = vlc_tick_now();
    sub->i_channel = *channel = channel_id;
    sub->i_order = 0;
    sub->b_ephemer = true;
    vout_PutSubpicture( owner->p_vout, sub );

    vlc_fifo_Unlock(owner->p_fifo);
    return VLC_SUCCESS;
}

int vlc_input_decoder_DelVoutOverlay( vlc_input_decoder_t *owner, size_t channel )
{
    assert( owner->dec.fmt_in->i_cat == VIDEO_ES );

    vlc_fifo_Lock(owner->p_fifo);

    if( !owner->p_vout )
    {
        vlc_fifo_Unlock(owner->p_fifo);
        return VLC_EGENERIC;
    }
    vout_UnregisterSubpictureChannel( owner->p_vout, channel );

    vlc_fifo_Unlock(owner->p_fifo);
    return VLC_SUCCESS;
}

int vlc_input_decoder_SetSpuHighlight( vlc_input_decoder_t *p_owner,
                                       const vlc_spu_highlight_t *spu_hl )
{
    assert( p_owner->dec.fmt_in->i_cat == SPU_ES );

    if( p_owner->p_sout_input )
        sout_InputControl( p_owner->p_sout, p_owner->p_sout_input,
                           SOUT_INPUT_SET_SPU_HIGHLIGHT, spu_hl );

    vlc_fifo_Lock(p_owner->p_fifo);
    if( !p_owner->p_vout )
    {
        vlc_fifo_Unlock(p_owner->p_fifo);
        return VLC_EGENERIC;
    }

    vout_SetSpuHighlight( p_owner->p_vout, spu_hl );

    vlc_fifo_Unlock(p_owner->p_fifo);
    return VLC_SUCCESS;
}
